package mmc.resource.models.collada;

import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL11.*;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import mmc.EngineObject;
import mmc.resource.Texture;
import mmc.resource.TextureLoader;

import org.collada.x2005.x11.colladaSchema.COLLADADocument;
import org.collada.x2005.x11.colladaSchema.GeometryDocument.Geometry;
import org.collada.x2005.x11.colladaSchema.ImageDocument.Image;
import org.collada.x2005.x11.colladaSchema.InstanceGeometryDocument.InstanceGeometry;
import org.collada.x2005.x11.colladaSchema.LibraryGeometriesDocument.LibraryGeometries;
import org.collada.x2005.x11.colladaSchema.MeshDocument.Mesh;
import org.collada.x2005.x11.colladaSchema.PolygonsDocument.Polygons;
import org.collada.x2005.x11.colladaSchema.PolylistDocument.Polylist;
import org.collada.x2005.x11.colladaSchema.TrianglesDocument.Triangles;
import org.lwjgl.opengl.GL11;

public class ColladaGeometry { //(convex)mesh supported only (no splines)
	private int arbVertex = 0, arbIndex = 0, vertexLengthBytes = 3, vertexCount = 0;
	private boolean hasNormals, hasTexCoords;
	private Texture texture;
	
	public ColladaGeometry(COLLADADocument doc, InstanceGeometry igeom) {
		this(doc, igeom.getUrl());
	}
		
	public ColladaGeometry(COLLADADocument doc, String url) {	
		Mesh mesh = resolveGeometry(doc, url).getMesh();
		List<ColladaTriangleMeshGenerator.TriangleMesh> meshes = new ArrayList<ColladaTriangleMeshGenerator.TriangleMesh>();
		
		for(Polylist p : mesh.getPolylistArray())
			meshes.add(ColladaTriangleMeshGenerator.generateTriangleMesh(doc, mesh, p));
		
		for(Polygons p : mesh.getPolygonsArray()){
			meshes.add(ColladaTriangleMeshGenerator.generateTriangleMesh(doc, mesh, p));
		}
		
		for(Triangles t : mesh.getTrianglesArray()){
			meshes.add(ColladaTriangleMeshGenerator.generateTriangleMesh(doc, mesh, t));
		}
		
		if(meshes.size()==0)
			throw new RuntimeException("Empty Geometry!");
		
		hasNormals = meshes.get(0).hasNormals;
		hasTexCoords = meshes.get(0).hasTexCoords;
		
		for(ColladaTriangleMeshGenerator.TriangleMesh tm: meshes){
			if(tm.hasNormals != hasNormals)
				throw new RuntimeException("normals not supplied evenly!");
			if(tm.hasTexCoords != hasTexCoords)
				throw new RuntimeException("texCoords not supplied evenly!");
			
			vertexCount += tm.vertices.size();
		}
		
		arbVertex = glGenBuffers();
		arbIndex = glGenBuffers();
		
		if(hasNormals) vertexLengthBytes += 3;
		if(hasTexCoords) vertexLengthBytes += 2;
		vertexLengthBytes *= 4;
		
		//Initialize Vertex Buffer
		int bufferSize = vertexCount * vertexLengthBytes;
		glBindBuffer(GL_ARRAY_BUFFER, arbVertex);
		glBufferData(GL_ARRAY_BUFFER, bufferSize, GL_STATIC_DRAW);
		ByteBuffer buffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, bufferSize, null);
		
		for(ColladaTriangleMeshGenerator.TriangleMesh tm: meshes)
			for(ColladaTriangleMeshGenerator.Vertex v : tm.vertices){
				buffer.putFloat(v.x);
				buffer.putFloat(v.y);
				buffer.putFloat(v.z);
				
				if(tm.hasNormals){
					buffer.putFloat(v.nx);
					buffer.putFloat(v.ny);
					buffer.putFloat(v.nz);
				}
				
				if(tm.hasTexCoords){
					buffer.putFloat(v.tx);
					buffer.putFloat(v.ty);
				}
			}
		
		buffer.flip();
		glUnmapBuffer(GL_ARRAY_BUFFER);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		bufferSize = vertexCount * 4;
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arbIndex);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferSize, GL_STATIC_DRAW);
		buffer = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, bufferSize, null);
		
		for(int i=0;i<vertexCount;i++){
			buffer.putInt(i);
		}
		
		buffer.flip();
		glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		
		loadTexture(doc, null);
		
		if(texture!=null != hasTexCoords)
			throw new RuntimeException("texture problem detected!");
	}
	
	private void loadTexture(COLLADADocument doc, InstanceGeometry igeom){
		/*
		if(igeom.getBindMaterial()!=null){
			Material mat = resolveMaterial(doc, igeom.getBindMaterial().getTechniqueCommon().getInstanceMaterialArray()[0].getTarget());
			Effect effect = resolveEffect(doc, mat.getInstanceEffect().getUrl());
			for(FxNewparamCommon i : effect.getNewparamArray()){
				System.out.println("newparam found!");
			}
		}*/
		
		//workaround - should work for models with 1 texture in collada 1.4.x or 1.5 format
		if(doc.getCOLLADA().getLibraryImagesArray().length>0)
			for(Image i : doc.getCOLLADA().getLibraryImagesArray()[0].getImageArray()){
				texture = TextureLoader.load(i.getInitFrom());
				return;
			}
	}
	
	public void render(EngineObject observer) {
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arbIndex);
		glEnableClientState(GL11.GL_VERTEX_ARRAY);
		glBindBuffer(GL_ARRAY_BUFFER, arbVertex);
		glVertexPointer(3, GL_FLOAT, vertexLengthBytes, 0);
		
		if(hasNormals){
			glEnableClientState(GL11.GL_NORMAL_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER, arbVertex);
			glNormalPointer(GL_FLOAT, vertexLengthBytes, 3*4);
		}

		glColor3f(1,1,1);
		if(hasTexCoords){
			glEnable(GL_TEXTURE_2D);
			texture.bind();
			
			glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER, arbVertex);
			glTexCoordPointer(2, GL_FLOAT, vertexLengthBytes, hasNormals?(6*4):(3*4));
		}else{
			glDisable(GL_TEXTURE_2D);
		}
		
		glDrawElements(GL_TRIANGLES,vertexCount,GL_UNSIGNED_INT,0);
		
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		
		if(hasTexCoords){
			glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		}else{
			glEnable(GL_TEXTURE_2D);
		}
		if(hasNormals)
			glDisableClientState(GL11.GL_NORMAL_ARRAY);
		glDisableClientState(GL11.GL_VERTEX_ARRAY);
	}
	
	private static Geometry resolveGeometry(COLLADADocument doc, String geometryUrl){
		assert geometryUrl.charAt(0) == '#';
		geometryUrl = geometryUrl.substring(1);
		
		LibraryGeometries[] scenes = doc.getCOLLADA().getLibraryGeometriesArray();
		
		for(LibraryGeometries s : scenes)
			for(Geometry ss : s.getGeometryArray())
				if(ss.getId().equals(geometryUrl)){
					return ss;
				}
		
		throw new RuntimeException("Invalid geometryURL: "+geometryUrl);
	}
	/*
	private static Material resolveMaterial(COLLADADocument doc, String materialUrl){
		assert materialUrl.charAt(0) == '#';
		materialUrl = materialUrl.substring(1);
		
		LibraryMaterials[] scenes = doc.getCOLLADA().getLibraryMaterialsArray();
		
		for(LibraryMaterials s : scenes)
			for(Material ss : s.getMaterialArray())
				if(ss.getId().equals(materialUrl)){
					return ss;
				}
		
		throw new RuntimeException("Invalid materialURL: "+materialUrl);
	}
	
	private static Image resolveImage(COLLADADocument doc, String imageUrl){
		assert imageUrl.charAt(0) == '#';
		imageUrl = imageUrl.substring(1);
		
		LibraryImages[] scenes = doc.getCOLLADA().getLibraryImagesArray();
		
		for(LibraryImages s : scenes)
			for(Image ss : s.getImageArray())
				if(ss.getId().equals(imageUrl)){
					return ss;
				}
		
		throw new RuntimeException("Invalid imageURL: "+imageUrl);
	}
	
	private static Effect resolveEffect(COLLADADocument doc, String effectUrl){
		assert effectUrl.charAt(0) == '#';
		effectUrl = effectUrl.substring(1);
		
		LibraryEffects[] scenes = doc.getCOLLADA().getLibraryEffectsArray();
		
		for(LibraryEffects s : scenes)
			for(Effect ss : s.getEffectArray())
				if(ss.getId().equals(effectUrl)){
					return ss;
				}
		
		throw new RuntimeException("Invalid effectURL: "+effectUrl);
	}*/
}